/*
线程池，详细注释见 threadPool/threadPool_Cpp
*/

#ifndef __threadPool_H__
#define __threadPool_H__


#include <functional>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <future>
#include <stdexcept>

class ThreadPool
{
public:
    explicit ThreadPool(uint32_t threadNum = std::thread::hardware_concurrency()):m_stop(false)
    {
        if(threadNum <= 0 || threadNum > std::thread::hardware_concurrency() * 10)
        {
            return;
        }

        for(uint32_t i = 0; i < threadNum; ++i)
        {
            m_worker.emplace_back([this](){

                std::function<void()> task;
                while(true)
                {
                    {
                        std::unique_lock<std::mutex> lock(m_taskQueueMutex);
                        m_condition.wait(lock, [this](){
                            if(this->m_stop || !this->m_taskQueue.empty())
                            {
                                return true;
                            }
                            return false;
                        });

                        if(this->m_stop && this->m_taskQueue.empty())
                        {
                            return;
                        }

                        task = std::move(this->m_taskQueue.front());
                        this->m_taskQueue.pop();
                    }

                    task();
                }
            });
        }
    }


    ~ThreadPool()
    {
        Stop();
        m_condition.notify_all();
        for(std::thread& worker: m_worker)
        {
            worker.join();
        }
    }

    uint32_t GetThreadNum() { return m_worker.size(); }

    template<typename F, typename... Args>
    auto EnQueue(F&& f, Args&&... args)
    {
        using return_type = std::invoke_result_t<F, Args...>;

        auto task = std::make_shared<std::packaged_task<return_type()>>
            (std::bind(std::forward<F>(f), std::forward<Args>(args)...));
        
        std::future<return_type> res = task->get_future();

        {
            std::unique_lock<std::mutex> lock(m_taskQueueMutex);

            if(m_stop)
            {
                throw std::runtime_error("enqueue on stopped Thread pool");
            }

            m_taskQueue.emplace([task = std::move(task)](){
                (*task)();
            });
        }

        m_condition.notify_one();
        return res;
    }

    void Stop()
    {
        std::unique_lock<std::mutex> lock(m_taskQueueMutex);
        m_stop = true;
    }

private:
    std::vector<std::thread> m_worker;
    std::queue<std::function<void()>> m_taskQueue;
    std::mutex m_taskQueueMutex;
    std::condition_variable m_condition;
    bool m_stop;
};


// ThreadPool::ThreadPool(uint32_t threadNum):m_stop(false)
// {
//     if(threadNum <= 0 || threadNum > std::thread::hardware_concurrency() * 10)
//     {
//         return;
//     }

//     for(uint32_t i = 0; i < threadNum; ++i)
//     {
//         m_worker.emplace_back([this](){

//             std::function<void()> task;
//             while(true)
//             {
//                 {
//                     std::unique_lock<std::mutex> lock(m_taskQueueMutex);
//                     m_condition.wait(lock, [this](){
//                         if(this->m_stop || !this->m_taskQueue.empty())
//                         {
//                             return true;
//                         }
//                         return false;
//                     });

//                     if(this->m_stop && this->m_taskQueue.empty())
//                     {
//                         return;
//                     }

//                     task = std::move(this->m_taskQueue.front());
//                     this->m_taskQueue.pop();
//                 }

//                 task();
//             }
//         });
//     }
// }

// template<typename F, typename... Args>
// auto ThreadPool::EnQueue(F&& f, Args&&... args)
// {
//     using return_type = std::invoke_result_t<F, Args...>;

//     auto task = std::make_shared<std::packaged_task<return_type()>>
//         (std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    
//     std::future<return_type> res = task->get_future();

//     {
//         std::unique_lock<std::mutex> lock(m_taskQueueMutex);

//         if(m_stop)
//         {
//             throw std::runtime_error("enqueue on stopped Thread pool");
//         }

//         m_taskQueue.emplace([task = std::move(task)](){
//             (*task)();
//         });
//     }

//     m_condition.notify_one();
//     return res;
// }


// ThreadPool::~ThreadPool()
// {
//     Stop();
//     m_condition.notify_all();
//     for(std::thread& worker: m_worker)
//     {
//         worker.join();
//     }
// }

// void ThreadPool::Stop()
// {
//     std::unique_lock<std::mutex> lock(m_taskQueueMutex);
//     m_stop = true;
// }

#endif
